package Assignment;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
 * Created by IntelliJ IDEA.
 * User: Sarah
 * Date: 17/08/2011
 * Time: 2:37:55 PM
 * To change this template use File | Settings | File Templates.
 */


public class UCGroupController extends JPanel implements ActionListener {

    int gateCycle = 20, cageCycle = 30, reassignCycle = 1000, gateWaitCycle = 5000;
    final int GOUP = 1, GODOWN = -1, FREE = 0;
    int numOfFloor, numOfLift;
    elevatorUI[] group;
    int[] distance, minOrder;
    int[] upCalls, downCalls, upAllocateFail, downAllocateFail;
    Timer reassignTimer;
    Timer[] waitCloseGate;
    floorUI outBtn;

    int LiftsHeight, LiftsWidth, outBtnHeight, outBtnWidth;

    UCGroupController(int numOfFloor, int numOfLift) {
        this.numOfFloor = numOfFloor;
        this.numOfLift = numOfLift;
        init();

        JPanel liftGroup = new JPanel();

        liftGroup.setLayout(new GridLayout(1, numOfLift, 10, 8));
        for (int i = 1; i <= numOfLift; i++)
            liftGroup.add(group[i]);

        liftGroup.setBackground(Color.WHITE);
        liftGroup.setBounds(1, 1, LiftsWidth, LiftsHeight);
        outBtn.setBounds(LiftsWidth + 3, 1, outBtnWidth, outBtnHeight);

        setLayout(null);
        add(liftGroup);
        add(outBtn);
    }

    /**
     * Adds action listeners for when up/down, open/close buttons are pushed
     */
    protected void init() {
        outBtn = new floorUI(numOfFloor);
        distance = new int[numOfFloor + 1];
        minOrder = new int[numOfFloor + 1];
        upAllocateFail = new int[numOfFloor + 1];
        downAllocateFail = new int[numOfFloor + 1];
        group = new elevatorUI[numOfLift + 1];
        for (int i = 1; i <= numOfLift; i++)
            group[i] = new elevatorUI(numOfFloor);


        upCalls = new int[numOfFloor + 1];
        downCalls = new int[numOfFloor + 1];

        for (int i = 0; i <= numOfFloor; i++) {
            upCalls[i] = downCalls[i] = 0;
            upAllocateFail[i] = downAllocateFail[i] = 0;
        }

        for (int i = 1; i <= numOfLift; i++) {
            group[i].gateTimer = new Timer(gateCycle, this);
            group[i].cageTimer = new Timer(cageCycle, this);
            group[i].cageTimer.start();
        }

        for (int i = 1; i <= numOfLift; i++) {
            for (int j = 1; j <= numOfFloor; j++) {
                group[i].innerBtn.stopButton[j].addActionListener(this);
            }

            group[i].innerBtn.closeButton.addActionListener(this);
            group[i].innerBtn.openButton.addActionListener(this);
        }

        for (int j = 1; j <= numOfFloor; j++) {
            outBtn.UP[j].addActionListener(this);
            outBtn.DOWN[j].addActionListener(this);
        }

        for (int i = 1; i <= numOfLift; i++) {
            group[i].liftState.enterButton.addActionListener(this);
            group[i].liftState.exitButton.addActionListener(this);
        }

        reassignTimer = new Timer(reassignCycle, this);
        reassignTimer.start();

        waitCloseGate = new Timer[numOfFloor + 1];
        for (int i = 1; i <= numOfLift; i++)
            waitCloseGate[i] = new Timer(gateWaitCycle, this);


        LiftsHeight = group[1].PanelHeight + 30;
        LiftsWidth = numOfLift * (group[1].PanelWidth + 20);
        outBtnHeight = group[1].PanelHeight;
        outBtnWidth = 145;
    }

    /**
     * Listens for the up button and responds accordingly
     */
    public void actionPerformed(ActionEvent e) {
        for (int i = 1; i < numOfFloor; i++) {
            if (e.getSource() == outBtn.UP[i]) {
                if (upCalls[i] == 0) {
                    upCalls[i] = 1;
                    chooseUp(i);
                }
            }
        }

        /**
         * Listens for the down button and responds accordingly
         */
        for (int i = 2; i <= numOfFloor; i++) {
            if (e.getSource() == outBtn.DOWN[i]) {
                if (downCalls[i] == 0) {
                    downCalls[i] = 1;
                    chooseDown(i);
                }
            }
        }

        /**
         * Listens for the elevator shaft timer and responds accordingly
         */
        for (int j = 1; j <= numOfLift; j++) {
            if (e.getSource() == group[j].cageTimer) {
                group[j].liftState.liftInfo3.setText("");
                if (group[j].state == GOUP) {
                    group[j].liftState.liftInfo3.setForeground(Color.BLACK);
                    group[j].liftState.liftInfo3.setText("");
                    group[j].cage.moveUp();
                    if (group[j].cage.relativeHeight < -group[j].cage.heightOfFloor) {
                        group[j].floor++;
                        group[j].cage.floor++;
                        setColorFloorNum(j, group[j].floor);
                        group[j].cage.relativeHeight = 0;
                        testStop(j, group[j].floor);
                    }

                } else if (group[j].state == GODOWN) {
                    group[j].liftState.liftInfo3.setForeground(Color.BLACK);
                    group[j].liftState.liftInfo3.setText("");
                    group[j].cage.moveDown();
                    if (group[j].cage.relativeHeight > group[j].cage.heightOfFloor) {
                        group[j].floor--;
                        group[j].cage.floor--;
                        setColorFloorNum(j, group[j].floor);
                        group[j].cage.relativeHeight = 0;
                        testStop(j, group[j].floor);
                    }
                }
            }
        }

        /**
         * Listens for the open button and responds accordingly
         */
        for (int j = 1; j <= numOfLift; j++) {
            if (e.getSource() == group[j].innerBtn.openButton) {
                group[j].ifOpen = true;
                if (group[j].state == FREE)
                    group[j].gateTimer.start();
            }
        }

        /**
         * Listens for the close button and responds accordingly
         */
        for (int j = 1; j <= numOfLift; j++) {
            if (e.getSource() == group[j].innerBtn.closeButton) {
                group[j].ifOpen = false;
                group[j].gateTimer.start();
            }
        }

        /**
         * Listens for the stop button and responds accordingly
         */
        for (int j = 1; j <= numOfLift; j++) {
            for (int i = 1; i <= numOfFloor; i++) {
                if (e.getSource() == group[j].innerBtn.stopButton[i]) {
                    if (group[j].state == FREE && group[j].floor == i) {
                        group[j].ifOpen = true;
                        group[j].gateTimer.start();
                    } else {
                        if ((group[j].floor != i) || ((group[j].floor == i) && (group[j].cage.relativeHeight != 0))) {
                            group[j].stop[i] = 1;
                            group[j].innerBtn.stopButton[i].setForeground(Color.RED);
                            getHighestLowestCalls(j, i);
                        }
                    }
                }
            }
        }

        /**
         * Listens for the elevator door button and responds accordingly
         */
        for (int j = 1; j <= numOfLift; j++) {
            if (e.getSource() == group[j].gateTimer) {
                if (group[j].ifOpen) {
                    openGate(j);
                } else
                    closeGate(j);
            }
        }

        /**
         * Listens for the reassign timer button and responds accordingly
         */
        if (e.getSource() == reassignTimer) {
            for (int i = 1; i <= numOfFloor; i++) {
                if (downAllocateFail[i] == 1) {
                    chooseDown(i);
                }
                if (upAllocateFail[i] == 1) {
                    chooseUp(i);
                }
            }
        }

        for (int j = 1; j <= numOfLift; j++) {
            if (e.getSource() == waitCloseGate[j]) {
                group[j].gateTimer.start();
                closeGate(j);
                waitCloseGate[j].stop();
            }
        }

        /**
         * Listens for the state of the elevator and responds accordingly
         */
        for (int i = 1; i <= numOfLift; i++) {
            int m = group[i].liftState.getNum();
            if (group[i].cage.movement > 0) {
                if (e.getSource() == group[i].liftState.enterButton) {
                    group[i].ifOpen = true;
                    if (m + 1 <= 10) {
                        m += 1;
                        if (m >= 0) group[i].liftState.liftInfo3.setText("");
                        group[i].liftState.setNum(m);

                    } else {
                        group[i].liftState.liftInfo3.setText("");
                        group[i].liftState.liftInfo3.setForeground(Color.RED);
                        m = 10;
                    }
                } else if (e.getSource() == group[i].liftState.exitButton) {
                    group[i].ifOpen = true;
                    if (m - 1 >= 0) {
                        m -= 1;
                        group[i].liftState.setNum(m);
                        if (m <= 10) group[i].liftState.liftInfo3.setText("");
                    } else {
                        m = 0;
                        group[i].liftState.liftInfo3.setText("");
                        group[i].liftState.liftInfo3.setForeground(Color.RED);
                    }
                }
            }
            if (m == 0) group[i].liftState.liftInfo1.setText("");
            else group[i].liftState.liftInfo1.setText("");
            group[i].liftState.liftInfo2.setText("");
        }
    }

    /**
     * Opens the elevator door
     */
    void openGate(int j) {
        group[j].cageTimer.stop();
        if ((group[j].cage.movement > group[j].cage.widthUpBound)) {
            group[j].gateTimer.stop();

            if (group[j].ifOpen == true) {
                waitCloseGate[j].start();
                group[j].ifOpen = false;

            }
        } else {
            group[j].cage.openGate();
        }
    }

    /**
     * Closes the elevator door
     */
    void closeGate(int j) {
        group[j].cageTimer.stop();

        if ((group[j].cage.movement < group[j].cage.widthLowBound)) {

            group[j].gateTimer.stop();
            searchHighestLowest(j);
            group[j].cageTimer.start();
        } else {
            group[j].cage.closeGate();
        }
    }

    /**
     * Tells the elevator where to go depending on which buttons have been pressed
     */
    public void getHighestLowestCalls(int callLift, int callfloor) {
        if (group[callLift].highestCall > numOfFloor || group[callLift].highestCall < callfloor)
            group[callLift].highestCall = callfloor;
        if (group[callLift].lowestCall == 0 || group[callLift].lowestCall > callfloor)
            group[callLift].lowestCall = callfloor;
        setState(callLift, callfloor);
    }

    /**
     * Sets the state of the elevator
     */
    public void setState(int callLift, int callfloor) {
        int floor = group[callLift].floor;
        if (floor == numOfFloor || floor == 1)
            group[callLift].state = FREE;

        if (group[callLift].highestCall > numOfFloor || group[callLift].lowestCall == 0) {
            group[callLift].state = FREE;

        } else if (group[callLift].state == FREE) {

            if (group[callLift].highestCall < floor) {
                group[callLift].state = GODOWN;

            } else if (group[callLift].highestCall > floor) {
                group[callLift].state = GOUP;

            } else {
                group[callLift].cageTimer.stop();
                if (group[callLift].upStop[floor] == 1) {
                    group[callLift].state = GOUP;
                    group[callLift].upStop[floor] = 0;
                    upCalls[floor] = 0;
                    setUpButtonBlack(floor);
                }

                if (group[callLift].downStop[floor] == 1) {
                    group[callLift].state = GODOWN;
                    group[callLift].downStop[floor] = 0;
                    downCalls[floor] = 0;
                    setDownButtonBlack(floor);
                }
                group[callLift].gateTimer.start();
                group[callLift].ifOpen = true;
            }
        } else if (group[callLift].state == GOUP) {
            if (floor > group[callLift].highestCall) {
                group[callLift].state = GODOWN;
            }

        } else if (group[callLift].state == GODOWN) {
            if (floor < group[callLift].lowestCall)
                group[callLift].state = GOUP;
        }
    }

    /**
     * Gathers the highest and lowest floor numbers that have been pressed
     */
    void searchHighestLowest(int callLift) {
        int t = callLift;
        for (int i = numOfFloor; i > 0; i--) {
            if (group[t].stop[i] == 1 || group[t].upStop[i] == 1 || group[t].downStop[i] == 1) {
                group[t].highestCall = i;
                break;
            } else {
                group[t].highestCall = 10000;
            }
        }

        for (int i = 1; i <= numOfFloor; i++) {
            if (group[t].stop[i] == 1 || group[t].upStop[i] == 1 || group[t].downStop[i] == 1) {
                group[t].lowestCall = i;
                break;
            } else {
                group[t].lowestCall = 0;
            }
        }
        setState(t, 0);
    }

    /**
     * Moves the elevator
     */
    void testStop(int callLift, int floor) {
        int t = callLift;
        if (group[t].stop[floor] == 1) {
            group[t].cageTimer.stop();
            group[t].stop[floor] = 0;
            group[t].innerBtn.stopButton[floor].setForeground(Color.BLACK);

            if (group[t].state == GODOWN && downCalls[floor] == 1) {
                group[t].downStop[floor] = 0;
                cancelAllDownstop(floor);
                downCalls[floor] = 0;
                researchAll(t);
                setDownButtonBlack(floor);
            }

            if (group[t].state == GOUP && upCalls[floor] == 1) {
                group[t].upStop[floor] = 0;
                cancelAllUpstop(floor);
                upCalls[floor] = 0;
                researchAll(t);
                setUpButtonBlack(floor);
            }

            group[t].gateTimer.start();
            group[t].ifOpen = true;

        } else if (group[t].state == GODOWN && downCalls[floor] == 1) {
            group[t].cageTimer.stop();
            group[t].downStop[floor] = 0;
            outBtn.DOWN[floor].setForeground(Color.BLACK);
            setDownButtonBlack(floor);

            cancelAllDownstop(floor);
            downCalls[floor] = 0;
            researchAll(t);

            group[t].gateTimer.start();
            group[t].ifOpen = true;

        } else if (group[t].state == GOUP && upCalls[floor] == 1) {
            group[t].cageTimer.stop();
            group[t].upStop[floor] = 0;
            outBtn.UP[floor].setForeground(Color.BLACK);
            setUpButtonBlack(floor);

            cancelAllUpstop(floor);
            upCalls[floor] = 0;
            researchAll(t);

            group[t].gateTimer.start();
            group[t].ifOpen = true;

        } else if (group[t].state == GOUP && floor == group[t].highestCall) {

            if (downCalls[floor] == 1 && group[t].upStop[floor] == 0) {

                group[t].cageTimer.stop();
                group[t].downStop[floor] = 0;

                setDownButtonBlack(floor);
                group[t].state = GODOWN;
                group[t].liftState.liftInfo3.setText("");
                group[t].gateTimer.start();
                group[t].ifOpen = true;

                cancelAllDownstop(floor);
                downCalls[floor] = 0;
                researchAll(t);
            }

        } else if (group[t].state == GODOWN && floor == group[t].lowestCall) {

            if (upCalls[floor] == 1 && group[t].downStop[floor] == 0) {
                group[t].cageTimer.stop();

                cancelAllUpstop(floor);
                upCalls[floor] = 0;
                researchAll(t);

                setUpButtonBlack(floor);
                group[t].state = GOUP;
                group[t].liftState.liftInfo3.setText("");
                group[t].gateTimer.start();
                group[t].ifOpen = true;
            }

        } else if (floor == numOfFloor || floor == 1) {
            group[t].state = FREE;
            group[t].gateTimer.start();
            group[t].ifOpen = true;
        }
    }

    /**
     * Respond if the up button is pressed
     */
    public void chooseUp(int upCallFloor) {
        int success = 0;
        setUpButtonRed(upCallFloor);
        selectMinDistance(upCallFloor);

        for (int i = 1; i <= numOfLift; i++) {
            success = upChooseForOne(minOrder[i], upCallFloor);
            if (success == 1) {
                upAllocateFail[upCallFloor] = 0;
                searchHighestLowest(minOrder[i]);
                break;
            }
        }

        if (success == 0) {
            for (int i = 1; i <= numOfLift; i++) {
                if (group[minOrder[i]].state == FREE) {
                    upAllocateFail[upCallFloor] = 0;
                    success = 1;
                    group[minOrder[i]].upStop[upCallFloor] = 1;
                    searchHighestLowest(minOrder[i]);
                    break;
                }
            }
        }

        if (success == 0)
            upAllocateFail[upCallFloor] = 1;
    }

    /**
     * Responds if the down button is pressed
     */
    public void chooseDown(int downCallFloor) {
        int success = 0;
        setDownButtonRed(downCallFloor);
        selectMinDistance(downCallFloor);
        for (int i = 1; i <= numOfLift; i++) {
            success = downChooseForOne(minOrder[i], downCallFloor);
            if (success == 1) {
                downAllocateFail[downCallFloor] = 0;
                searchHighestLowest(minOrder[i]);
                break;
            }
        }

        if (success == 0) {
            for (int i = 1; i <= numOfLift; i++) {
                if (group[minOrder[i]].state == FREE) {
                    downAllocateFail[downCallFloor] = 0;
                    success = 1;
                    group[minOrder[i]].downStop[downCallFloor] = 1;
                    searchHighestLowest(minOrder[i]);
                    break;
                }
            }
        }
        if (success == 0)
            downAllocateFail[downCallFloor] = 1;
    }

    /**
     * Select the minimum distance
     */
    public void selectMinDistance(int callFloor) {

        int callHeight = (callFloor - 1) * group[1].cage.heightOfFloor;
        for (int i = 1; i <= numOfLift; i++)
            distance[i] = Math.abs(group[i].cage.currentHeight - callHeight);

        int req = ((int) (Math.random() * numOfLift)) + 1;
        for (int i = 1; i <= numOfLift; i++) {
            int k = i;
            for (int j = 1; j <= numOfLift; j++) {
                if (distance[j] < distance[k])
                    k = j;
            }

            if (distance[req] == distance[k]) {
                k = req;
                req = (req + 1) % (numOfLift + 1);
            }
            minOrder[i] = k;
            distance[k] = numOfLift + 100000;
        }
    }

    /**
     * Sets the up button as black when it has not been pushed or the elevator has arrived
     */
    public void setUpButtonBlack(int floor) {
        for (int i = 1; i <= numOfLift; i++) {
            outBtn.UP[floor].setForeground(Color.BLACK);
        }
    }

    /**
     * Sets the up button as red when it has been pushed or the elevator has not arrived
     */
    public void setUpButtonRed(int floor) {
        for (int i = 1; i <= numOfLift; i++) {
            outBtn.UP[floor].setForeground(Color.RED);
        }
    }

    /**
     * Sets the down button as black when it has not been pushed or the elevator has arrived
     */
    public void setDownButtonBlack(int floor) {
        for (int i = 1; i <= numOfLift; i++) {
            outBtn.DOWN[floor].setForeground(Color.BLACK);
        }
    }

    /**
     * Sets the down button as red when it has been pushed or the elevator has not arrived
     */
    public void setDownButtonRed(int floor) {
        for (int i = 1; i <= numOfLift; i++) {
            outBtn.DOWN[floor].setForeground(Color.RED);
        }
    }

    /**
     * Sets the elevator shaft when the elevator is moving up
     */
    public int upChooseForOne(int callLif, int upCallFloor) {
        int callHeight = (upCallFloor - 1) * group[1].cage.heightOfFloor;
        if (group[callLif].state == GOUP) {
            if (group[callLif].floor < upCallFloor) {
                group[callLif].upStop[upCallFloor] = 1;

                return 1;
            } else if (group[callLif].floor == upCallFloor && group[callLif].cage.relativeHeight == 0) {
                group[callLif].upStop[upCallFloor] = 0;

                upCalls[upCallFloor] = 0;

                setUpButtonBlack(upCallFloor);
                group[callLif].gateTimer.start();
                group[callLif].ifOpen = true;
                return 1;
            }
        } else if (group[callLif].state == FREE) {
            if (group[callLif].cage.floor < upCallFloor) {
                group[callLif].upStop[upCallFloor] = 1;
                return 1;
            } else if (group[callLif].cage.floor == upCallFloor && group[callLif].cage.relativeHeight == 0) {
                group[callLif].upStop[upCallFloor] = 1;
                return 1;
            }
        }
        return 0;
    }

    /**
     * Sets the elevator shaft when the elevator is moving down
     */
    public int downChooseForOne(int callLif, int downCallFloor) {
        if (group[callLif].state == GODOWN) {
            if (group[callLif].floor > downCallFloor) {
                group[callLif].downStop[downCallFloor] = 1;
                return 1;
            } else if (group[callLif].floor == downCallFloor && group[callLif].cage.relativeHeight == 0) {
                group[callLif].downStop[downCallFloor] = 0;
                downCalls[downCallFloor] = 0;
                setDownButtonBlack(downCallFloor);

                group[callLif].gateTimer.start();
                group[callLif].ifOpen = true;
                return 1;
            }
        } else if (group[callLif].state == FREE) {
            if (group[callLif].cage.floor > downCallFloor) {
                group[callLif].downStop[downCallFloor] = 1;
                return 1;
            } else if (group[callLif].cage.floor == downCallFloor && group[callLif].cage.relativeHeight == 0) {
                group[callLif].downStop[downCallFloor] = 1;
                return 1;
            }
        }
        return 0;
    }

    /**
     * Sets the colour of the floor numbers
     */
    void setColorFloorNum(int lift, int floor) {
        for (int i = 1; i <= numOfFloor; i++)
            group[lift].tag.floorNum[i].setForeground(Color.BLUE);
        group[lift].tag.floorNum[floor].setForeground(Color.RED);
    }

    /**
     * Discovers the highest and lowest elevator floor numbers
     */
    void researchAll(int j) {
        for (int i = 1; i <= numOfLift; i++)
            if (i != j) searchHighestLowest(i);
    }

    /**
     * Stops the elevator moving up
     */
    void cancelAllUpstop(int j) {
        for (int i = 1; i <= numOfLift; i++)
            group[i].upStop[j] = 0;
    }

    /**
     * Stops the elevator moving down
     */
    void cancelAllDownstop(int j) {
        for (int i = 1; i <= numOfLift; i++)
            group[i].downStop[j] = 0;
    }
}